package com.hexb.core.mybatis

import com.hexb.core.mybatis.entry.SqlEntry
import org.apache.ibatis.mapping.*
import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
import org.apache.ibatis.session.Configuration
import org.apache.log4j.Logger

/**
 * @Package : com.hexb.mt.helper
 * @Author : hexb 
 * @Date : 2018-08-11 15:26
 */
class SQLSelectBuilder {

    static private final Logger logger = Logger.getLogger(SQLSelectBuilder.class)

    static private final String select = 'select'
    static private final String selectOne = 'selectOne'
    static private final String selectByMap = 'selectByMap'
    static private final String count = 'count'
    static private final String countByEntry = 'countByEntry'
    static private final String selectWithPK = 'selectWithPK'
    static private final String selectWithoutPK = 'selectWithoutPK'
    static private final String selectOneByEntry = 'selectOneByEntry'

    static MappedStatement selectByMap(SqlEntry entry, Configuration configuration) {
        try {
            String sql = [buildSelect(entry, configuration), buildMultipleWhere(entry, configuration), buildOrderBy(entry)].join(' ')
            SqlSource sqlSource = new XMLLanguageDriver().createSqlSource(configuration, "<script>${sql}</script>", Map.class)

            String id = "${entry.namespace}.${selectByMap}"
            return selectMappedStatement(id, entry, sqlSource, configuration)
        } catch (e) {
            logger.error(e)
            throw e
        }
    }

    static MappedStatement selectWithPK(SqlEntry entry, Configuration configuration) {
        try {
            String sql = [buildSelect(entry, configuration), buildMultipleWithoutWhere(entry, configuration)].join(' ')
            SqlSource sqlSource = new XMLLanguageDriver().createSqlSource(configuration, "<script>${sql}</script>", Map.class)

            String id = "${entry.namespace}.${selectWithPK}"
            return selectMappedStatement(id, entry, sqlSource, configuration)
        } catch (e) {
            logger.error(e)
            throw e
        }
    }


    static MappedStatement selectWithoutPK(SqlEntry entry, Configuration configuration) {
        try {
            String sql = [buildSelect(entry, configuration), buildMultipleWithoutWhere(entry, configuration)].join(' ')
            SqlSource sqlSource = new XMLLanguageDriver().createSqlSource(configuration, "<script>${sql}</script>", Map.class)

            String id = "${entry.namespace}.${selectWithoutPK}"
            return selectMappedStatement(id, entry, sqlSource, configuration)
        } catch (e) {
            logger.error(e)
            throw e
        }
    }

    static MappedStatement select(SqlEntry entry, Configuration configuration) {
        try {
            String sql = [buildSelect(entry, configuration), buildMultipleWhere(entry, configuration), buildSort(entry)].join(' ')
            SqlSource sqlSource = new XMLLanguageDriver().createSqlSource(configuration, "<script>${sql}</script>", entry.entryClass)

            String id = "${entry.namespace}.${select}"
            return selectMappedStatement(id, entry, sqlSource, configuration)
        } catch (e) {
            logger.error(e)
            throw e
        }
    }

    static MappedStatement selectOneByEntry(SqlEntry entry, Configuration configuration) {
        try {
            String sql = [buildSelect(entry, configuration), buildMultipleWhere(entry, configuration), buildSort(entry)].join(' ')
            SqlSource sqlSource = new XMLLanguageDriver().createSqlSource(configuration, "<script>${sql} limit 0,1 </script>", entry.entryClass)

            String id = "${entry.namespace}.${selectOneByEntry}"
            return selectMappedStatement(id, entry, sqlSource, configuration)
        } catch (e) {
            logger.error(e)
            throw e
        }
    }

    static MappedStatement selectOne(SqlEntry entry, Configuration configuration) {
        try {
            String sql = [buildSelect(entry, configuration), "<where>`${entry.idEntry.column}`=#{_parameter}</where> limit 0,1"].join(' ')
            SqlSource sqlSource = new XMLLanguageDriver().createSqlSource(configuration, "<script>${sql}</script>", entry.idClass)
            String id = "${entry.namespace}.${selectOne}"
            return selectMappedStatement(id, entry, sqlSource, configuration)
        } catch (e) {
            logger.error(e)
            throw e
        }
    }

    static MappedStatement count(SqlEntry entry, Configuration configuration) {
        mapperCount(entry, configuration, count)
    }

    static MappedStatement countByEntry(SqlEntry entry, Configuration configuration) {
        mapperCount(entry, configuration, countByEntry)
    }

    private static MappedStatement mapperCount(SqlEntry entry, Configuration configuration, String selectId) {
        try {
            String sql = ["select count(1) as count from ${entry.tableName} ", buildMultipleWhere(entry, configuration)].join(' ')
            SqlSource sqlSource = new XMLLanguageDriver().createSqlSource(configuration, "<script>${sql}</script>", entry.idClass)
            String id = "${entry.namespace}.${selectId}"
            def mappedStatement = new MappedStatement.Builder(configuration, id,
                    sqlSource,
                    SqlCommandType.SELECT)
            def resultMapping = new ResultMapping.Builder(configuration, 'count', 'count', Integer.class).build()
            mappedStatement.resultMaps([new ResultMap.Builder(configuration, id, Integer.class, [resultMapping], true).build()])
            mappedStatement.build()
        } catch (e) {
            logger.error(e)
            throw e
        }
    }

    private static MappedStatement selectMappedStatement(String id,
                                                         SqlEntry entry,
                                                         SqlSource sqlSource,
                                                         Configuration configuration) {
        def mappedStatement = new MappedStatement.Builder(configuration, id, sqlSource, SqlCommandType.SELECT)
        mappedStatement.resultMaps([new ResultMap.Builder(configuration, id, entry.entryClass, entry.resultMappings, true).build()])
        mappedStatement.build()
    }


    private static String buildSelect(SqlEntry entry, Configuration configuration) {
        "select * from ${entry.tableName} "
    }

    private static String buildMultipleWhere(SqlEntry entry, Configuration configuration) {
        String columnWhere = entry.propertyMapping.collect {
            """
               <if test="${it.property}!=null">
                    and `${it.column}` = #{${it.property}}
               </if>
            """
        }.join(' ')
        columnWhere ? "<where>${columnWhere}</where>" : ''
    }

    /**
     * 排除主键
     * */
    private static String buildMultipleWithoutWhere(SqlEntry entry, Configuration configuration) {
        String columnWhere = entry.propertyMapping.findAll { it.property != entry.idEntry.property }.collect {
            """
               <if test="${it.property}!=null">
                    and `${it.column}` = #{${it.property}}
               </if>
            """
        }.join(' ')
        String withoutWhere = "<![CDATA[ and `${entry.idEntry.column}` <> #{${entry.idEntry.column}} ]]>"
        columnWhere ? "<where>${columnWhere} ${withoutWhere}</where>" : ''
    }

    private static String buildSort(SqlEntry entry) {
        "order by `${entry.orderBy ?: entry.idEntry.column}`"
    }

    private static String buildOrderBy(SqlEntry entry) {
        if (entry.orderBy) {
            return """
                <choose>
                    <when test="orderBy!=null">
                        order by \${orderBy}
                    </when>
                    <otherwise>
                        order by ${entry.orderBy}
                    </otherwise>
                </choose>    
            """
        }
        """
            <if test="orderBy!=null">
                order by \${orderBy}
            </if>
        """
    }

}
